//
//  XLPlayerParseHandler.m
//  FFMpegKitDemo
//
//  Created by ilongge on 2023/8/3.
//

#import "XLPlayerParseHandler.h"
#import "XLPlayerError.h"
#import "XLPlayerDecoder.h"

#import <libavformat/avformat.h>
#import <libavcodec/avcodec.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>
#import <libavutil/imgutils.h>


#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>

@interface XLPlayerParseHandler()
{
    AVFormatContext *_formatContext;
    
    int _videoStreamIndex;
    AVStream *_videoStream;
    int _videoWidth;
    int _videoHeight;
    float _videoFps;
    enum AVColorSpace _colorSpace;
    enum AVColorRange _colorRange;
    
    int _audioStreamIndex;
    AVStream *_audioStream;
    int _audioSampleRate;
    int _audioBitDepth;
    
    AVFrame *_orginFrame;
    AVFrame *_swsFrame;
    AVFrame *_swrFrame;
    
    BOOL _videoTrackContain;
    BOOL _audioTrackContain;
    
    BOOL _canReadNextFrame;
}  
@property (nonatomic, strong) XLPlayerDecoder *decoder;
@end

@implementation XLPlayerParseHandler
+ (instancetype)parseHandlerWithURL:(NSURL *)URL error:(NSError *__autoreleasing *)error
{
    XLPlayerParseHandler *parseHandler = [[XLPlayerParseHandler alloc] initWithURL:URL error:error];
    return parseHandler;
}

-(instancetype)initWithURL:(NSURL *)URL error:(NSError *__autoreleasing *)error
{
    if (URL == nil) {
        *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeUrlNil];
        return nil;
    }
    else if (URL.path == nil) {
        *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeUrlInvalid];
        return nil;
    }
    self = [super init];
    if (self) {
        _url = URL;
        _videoTrackContain = NO;
        _audioTrackContain = NO;
        _canReadNextFrame = YES;
    }
    return self;
}

- (void)prepareParseError:(NSError *__autoreleasing *)error
{
    _formatContext = [self createFormatContext];
    
    _orginFrame = av_frame_alloc();
    _swsFrame = av_frame_alloc();
    _swrFrame = av_frame_alloc();
    
    if (_formatContext == NULL) {
        *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeCreateFormatContextFailed];
        return;
    }
    _videoStreamIndex = [self streamIndexWithFormatContext:_formatContext isVideo:YES];
    if (_videoStreamIndex != -1) {
        _videoTrackContain = YES;
        _videoStream = _formatContext->streams[_videoStreamIndex];
        _videoWidth = _videoStream->codecpar->width;
        _videoHeight = _videoStream->codecpar->height;
        _videoFps = [self getFPS:_videoStream];
        _colorRange = _videoStream->codecpar->color_range;
        _colorSpace = _videoStream->codecpar->color_space;
        uint8_t *out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                                                                self->_videoWidth,
                                                                                self->_videoHeight,
                                                                                1));
        av_image_fill_arrays(_swsFrame->data,
                             _swsFrame->linesize,
                             out_buffer,
                             AV_PIX_FMT_YUV420P,
                             self->_videoWidth,
                             self->_videoHeight,
                             1);
        [self videoCanHwaccelPrediction:_videoStream];
    }
    _audioStreamIndex = [self streamIndexWithFormatContext:_formatContext isVideo:NO];
    if (_audioStreamIndex != -1) {
        _audioTrackContain = YES;
        _audioStream = _formatContext->streams[_audioStreamIndex];
        _audioSampleRate = _audioStream->codecpar->sample_rate;
        _audioBitDepth = _audioStream->codecpar->bits_per_coded_sample;
    }
    
    av_dump_format(_formatContext, 0, [_url.path UTF8String], 0);
    if (_videoStreamIndex == -1
        &&
        _audioStreamIndex == -1) {
        *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeFileInvalid];
        return;
    }
    else{
        NSError *_error = *error;
        _decoder = [XLPlayerDecoder decoderWithFormatContxt:_formatContext
                                                     config:_config
                                           videoStreamIndex:_videoStreamIndex
                                           audioStreamIndex:_audioStreamIndex
                                                      error:&_error];
    }
}

- (AVFormatContext *)formatContext
{
    return _formatContext;
}

- (int)videoStreamIndex
{
    return _videoStreamIndex;
}

-(int)audioStreamIndex
{
    return _audioStreamIndex;
}

-(void)startParse:(void (^)(BOOL, AVFrame *))handler
{
    if (_formatContext == NULL) {
        return;
    }
    AVPacket packet;
    if (_audioTrackContain || _videoTrackContain) {
        while (_canReadNextFrame) {
            int ret = av_read_frame(self->_formatContext, &packet);
            if (ret < 0) {
                _canReadNextFrame = NO;
                handler(NO, NULL);
            }
            else{
                if (_videoTrackContain && (packet.stream_index == self->_videoStreamIndex)) {
                    [self.decoder decoderVideoPacket:packet frame:_orginFrame yuvFrame:_swsFrame];
                    handler(YES, _swsFrame);
//                    _canReadNextFrame = NO;
                }
                else if (_audioTrackContain && (packet.stream_index == self->_audioStreamIndex)) {
                    [self.decoder decoderAudioPacket:packet frame:_orginFrame swrFrame:_swrFrame];
                    handler(NO, _swrFrame);
                }
            }
            av_packet_unref(&packet);
        }
    }
}

- (void)readNext:(BOOL)next
{
    _canReadNextFrame = next;
}

#pragma mark -------------------------

- (AVFormatContext *)createFormatContext
{
    AVFormatContext  *formatContext = NULL;
    AVDictionary     *opts          = NULL;
    
    av_dict_set(&opts, "timeout", "1000000", 0);
    
    formatContext = avformat_alloc_context();
    int ret = 0;
    const char *path = [_url.path cStringUsingEncoding:NSUTF8StringEncoding];
    ret = avformat_open_input(&formatContext, path, NULL, &opts);
    if (ret != 0) {
        if (formatContext) {
            avformat_free_context(formatContext);
        }
        return NULL;
    }
    av_dict_free(&opts);
    ret = avformat_find_stream_info(formatContext, NULL);
    if (ret < 0) {
        avformat_close_input(&formatContext);
        return NULL;
    }
    return formatContext;
}

- (int)streamIndexWithFormatContext:(AVFormatContext *)formatContext
                            isVideo:(BOOL)isVideo
{
    int streamIndex = -1;
    for (int index = 0; index < formatContext->nb_streams; index ++) {
        enum AVMediaType streamType = formatContext->streams[index]->codecpar->codec_type;
        if (isVideo && streamType == AVMEDIA_TYPE_VIDEO) {
            streamIndex = index;
            break;
        }
        else if (isVideo == NO && streamType == AVMEDIA_TYPE_AUDIO) {
            streamIndex = index;
            break;
        }
    }
    return streamIndex;
}

- (float)getFPS:(AVStream *)stream
{
    float fps = 0.0;
    AVRational avg_rate = stream->avg_frame_rate;
    AVRational r_rate = stream->r_frame_rate;
    if (avg_rate.num && avg_rate.den) {
        fps = av_q2d(avg_rate);
    }
    else if (r_rate.num && r_rate.den) {
        fps = av_q2d(r_rate);
    }
    else{
        if (stream->time_base.num && stream->time_base.den) {
            fps = 1.0 / av_q2d(stream->time_base);
        }
    }
    return fps;
}
// 不保证一定准确，视频规格太高也可能没法硬解码
- (BOOL)videoCanHwaccelPrediction:(AVStream *)stream
{
    enum AVCodecID codeId = stream->codecpar->codec_id;
    CMVideoCodecType codecType = -1;
    switch (codeId) {
        case AV_CODEC_ID_H264:
            codecType = kCMVideoCodecType_H264;
            break;
        case AV_CODEC_ID_HEVC:
            codecType = kCMVideoCodecType_HEVC;
            break;
        default:
            break;
    }
    BOOL isSupported = VTIsHardwareDecodeSupported(codecType);
    return isSupported;
}

@end
